﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace DynamicToSql
{
    public interface ISession : IDisposable
    {
        ITransaction StartTransaction();

        IStatement Sql(string sql);

        IStatement Procedure(string name);

        IStatement Function(string name);

        IList<dynamic> Find(string name);

        IList<dynamic> Find(string name, PropertySelector selector);

        IList<dynamic> Find(string name, object criteria);

        IList<dynamic> Find(string name, object criteria, PropertySelector selector);

        IList<dynamic> Find(string name, Criteria criteria);

        IList<dynamic> Find(string name, Criteria criteria, PropertySelector selector);

        int Count(string name);

        int Count(string name, object criteria);

        int Count(string name, Criteria criteria);

        void Save(string name, Entity instance);

        void Save(string name, Entity instance, PropertySelector selector);

        int Update(string name, Entity instance, object criteria);

        int Update(string name, Entity instance, object criteria, PropertySelector selector);

        int Update(string name, Entity instance, Criteria criteria);

        int Update(string name, Entity instance, Criteria criteria,  PropertySelector selector);

        int Delete(string name);

        int Delete(string name, object criteria);

        int Delete(string name, Criteria criteria);

        ITransaction Transaction { get; }

        IDbConnection Connection { get; }

        ILogger Logger { get; }

        INamingConverter NamingConverter { get; }

        string ParameterPrefix { get; }
    }

    internal sealed class Session : ISession
    {
        private bool disposed;

        private Session()
        {

        }


        internal Session(IDbConnection connection, ILogger logger, INamingConverter namingConverter, string parameterPrefix)
        {
            this.Connection = connection;
            this.Connection.Open();

            this.Logger = logger;
            this.NamingConverter = namingConverter ?? new NamingConverter();
            this.ParameterPrefix = parameterPrefix ?? "@";
        }

        ~Session()
        {
            Dispose(false);
        }

        public ITransaction StartTransaction()
        {
            this.Transaction = new Transaction(this.Connection.BeginTransaction());
            return Transaction;
        }

        public IStatement Sql(string sql)
        {
            return new Statement(this, sql);
        }

        public IStatement Procedure(string name)
        {
            return new Statement(this, name, StatementType.StoredProcedure);
        }

        public IStatement Function(string name)
        {
            return new Statement(this, name, StatementType.Function);
        }

        public IList<dynamic> Find(string name)
        {
            return Find(name, null as PropertySelector);
        }

        public IList<dynamic> Find(string name, PropertySelector selector)
        {
            var sql = SqlHelper.GenerateSelect(this, name, selector);
            var stmt = this.Sql(sql);
            return stmt.ToList();
        }

        public IList<dynamic> Find(string name, object criteria)
        {
            return Find(name, criteria, null);
        }

        public IList<dynamic> Find(string name, object criteria, PropertySelector selector)
        {
            return Find(name, new Criteria(criteria), selector);
        }

        public IList<dynamic> Find(string name, Criteria criteria)
        {
            return Find(name, criteria, null);
        }

        public IList<dynamic> Find(string name, Criteria criteria, PropertySelector selector)
        {
            var sql = string.Format("{0} WHERE {1}", SqlHelper.GenerateSelect(this, name, selector), 
                                                     SqlHelper.GenerateCriteria(this, criteria));
            var stmt = this.Sql(sql);
            StatementHelper.AddParameters(stmt, criteria);
            return stmt.ToList();
        }

        public int Count(string name)
        {
            var stmt = this.Sql(SqlHelper.GenerateCount(this, name));
            return stmt.ToScalar<int>();
        }

        public int Count(string name, object criteria)
        {
            return Count(name, new Criteria(criteria));
        }

        public int Count(string name, Criteria criteria)
        {
            var sql = string.Format("{0} WHERE {1}", SqlHelper.GenerateCount(this, name), 
                                                     SqlHelper.GenerateCriteria(this, criteria));
            var stmt = this.Sql(sql);
            StatementHelper.AddParameters(stmt, criteria);
            return stmt.ToScalar<int>();
        }

        public void Save(string name, Entity instance)
        {
            Save(name, instance, null);
        }

        public void Save(string name, Entity instance, PropertySelector selector)
        {
            var stmt = this.Sql(SqlHelper.GenerateInsert(this, name, instance, selector));
            StatementHelper.AddParameters(stmt, instance, selector);
            stmt.Execute();
        }

        public int Update(string name, Entity instance, object criteria)
        {
            return Update(name, instance, criteria, null);
        }

        public int Update(string name, Entity instance, object criteria, PropertySelector selector)
        {
            return Update(name, instance, new Criteria(criteria), selector);
        }

        public int Update(string name, Entity instance, Criteria criteria)
        {
            return Update(name, instance, criteria, null);
        }

        public int Update(string name, Entity instance, Criteria criteria, PropertySelector selector)
        {
            var sql = string.Format("{0} WHERE {1}", SqlHelper.GenerateUpdate(this, name, instance, selector), 
                                                     SqlHelper.GenerateCriteria(this, criteria));
            var stmt = this.Sql(sql);
            StatementHelper.AddParameters(stmt, instance, selector);
            StatementHelper.AddParameters(stmt, criteria);
            return stmt.Execute();
        }

        public int Delete(string name)
        {
            var stmt = this.Sql(SqlHelper.GenerateDelete(this, name));
            return stmt.Execute();
        }

        public int Delete(string name, object criteria)
        {
            return Delete(name, new Criteria(criteria));
        }

        public int Delete(string name, Criteria criteria)
        {
            var sql = string.Format("{0} WHERE {1}", SqlHelper.GenerateDelete(this, name), 
                                                     SqlHelper.GenerateCriteria(this, criteria));
            var stmt = this.Sql(sql);
            StatementHelper.AddParameters(stmt, criteria);
            return stmt.Execute();
        }

        public void Dispose()
        {
            Dispose(true);

            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    if (Transaction != null)
                    {
                        Transaction.Dispose();
                        Transaction = null;
                    }

                    if (Connection != null)
                    {
                        Connection.Dispose();
                        Connection = null;
                    }
                }

            }
     
            disposed = true;
        }

        public ITransaction Transaction { get; private set; }

        public IDbConnection Connection { get; private set; }

        public ILogger Logger { get; private set; }

        public INamingConverter NamingConverter { get; private set; }

        public string ParameterPrefix { get; private set; }
    }

}
